"本Module无需nodejs或Browser环境";
import { nanoid } from "nanoid";
import _ from "lodash";
export * from "./geo.js";

export type Partial<T> = {
  [key in keyof T]?: T[key];
};

/**
 *
 * @param before 前缀
 * @returns 随机不重复/极小概率重复码
 */
export function getUuid(before: string = "abc-") {
  return (before || "") + nanoid();
}

/**
 * 使用Object.prototype.toString.call测试字面量类型
 * @param obj 要测试的
 * @returns 字面量类型
 */
export function getInstance(obj: any | never): Js.Types {
  const result = Object.prototype.toString.call(obj);
  return result.replace(/^\[object\s+/, "").replace(/\]$/, "");
}

/**
 * 根据最大length进行数组分割
 * @param arr 要分割的数组
 * @param maxLen 最大length
 * @returns 分割后的数组
 */
export function splitArrByRow<T>(
  arr: T[] = [],
  maxLen: number | undefined = 5
): T[][] {
  return _.chunk(arr, maxLen);
}

/**
 * 根据份数进行数组分割
 * @param arr 要分割的数组
 * @param cols 要分割的份数
 * @returns 分割后的数组
 */
export function splitArrByCol<T>(
  arr: T[] = [],
  cols: number | undefined = 3
): T[][] {
  cols = ~~(((+cols) ** 2) ** 0.5) || 1; // 先转换为正整数
  const maxLen = Math.ceil(arr.length / cols); // 例如总公共有5个，每份2列，分成3份
  return _.chunk(arr, maxLen);
}

/**
 * 等待一个毫秒数
 * @param interval 毫秒数
 */
export function sleep(interval: number): Promise<void> {
  return new Promise((resolve) => setTimeout(resolve, interval));
}

/**
 * 保留数字前后的"0"
 * @param num 传入的数字
 * @param frontLength 小数点前保留多少个0，如果数字位数大于frontLength，则不考虑
 * @param aftLength 小数点后保留多少个0，如果小数点后精度已高于aftLenth，则不考虑
 * @returns 整理后的数字
 */
export function keep0s(
  num: number | string = "",
  frontLength: number,
  aftLength: number = 0
): string {
  let finalArr: string[] = []; // 最终连起来的数组
  const isMinus = +num < 0; // 是否为负数
  if (isMinus) {
    finalArr.unshift("-"); //如果是负数，在开头加上"-"
  }
  let absValue = isMinus ? -num : num; // 转换为绝对值
  let splitted: string[] = (absValue + "").split(".");
  const { length: lenFront } = splitted[0];
  // -----处理小数点以前部分
  frontLength = ~~Math.abs(frontLength);
  if (lenFront < frontLength) {
    let _0sBeforeValue: string = Array.from({ length: frontLength - lenFront })
      .map(() => "0")
      .join("");
    finalArr.push(_0sBeforeValue);
  }
  finalArr.push(splitted[0]);
  // -----处理小数点以后部分
  aftLength = ~~Math.abs(aftLength);
  if (aftLength || splitted[1]) {
    finalArr.push("."); // 如果要求了小数点或者本身有小数点
    let afterStr = splitted[1] || ""; // 后面的部分
    finalArr.push(afterStr);
    let _0sAfterValue: string = Array.from({
      length: aftLength - afterStr.length,
    })
      .map(() => "0")
      .join(""); // 所补充的0
    finalArr.push(_0sAfterValue); // 加上所补充的0
  }
  return finalArr.join("");
}

export interface SecOf60sOptions {
  /**
   * 多少进制
   */
  base?: 60;
  /**
   * 分隔符
   */
  delimiter?: string | RegExp;
}

/**
 * 将60进制字符串转化成秒
 * @param options 第一个参数可以使选项，也可以是时间字符串
 * @param timeStrings 60进制时间字符串
 * @returns 总秒数
 */
export function secOf60s(
  options?: SecOf60sOptions | string,
  ...timeStrings: string[]
): number {
  const times = [options, ...timeStrings]; // 按值传递
  // 第一个参数是否为Object也就是选项
  const isFirstArgOptions =
    Object.prototype.toString.call(times[0]) === "[object Object]";
  // @ts-ignore
  const { base = 60, delimiter = /[:：]{1}/g }: SecOf60sOptions =
    isFirstArgOptions ? times.shift() : {};
  const reg = /^\s*-\s*/g;
  let secs = 0; // 总秒数
  for (const str of times) {
    // @ts-ignore
    const sgn = reg.test(str) ? -1 : 1; // 是否为负
    if (str) {
      // @ts-ignore
      [...str.replace(reg, "").replace(/\s+/g, "").split(delimiter)]
        .reverse()
        .forEach((ele, i) => (secs += base ** i * sgn * +ele));
    }
  }
  return secs;
}

export interface To60sFromSecOptions {
  /**
   * 小数点后保留多少位，默认为0
   */
  digits?: number;
  /**
   * 使用几个:分割，默认为1
   */
  colons?: number;
  /**
   * 多少进制，默认60
   */
  base?: number;
  /**
   * 使用什么分割，默认":"
   */
  delimiter?: string;
  /**
   * 是否根据进制保留前面的0，默认为true
   */
  keep0?: boolean;
}
/**
 * 从Number计算出60进制字符串
 * @param options 选项，可以不填
 * @param args 数字
 * @returns 计算出的60进制字符串
 */
export function to60sFromSec(
  options?: To60sFromSecOptions | number,
  ...args: number[]
): string {
  const times = [options, ...args]; // 按值传递
  // 第一个参数是否为Object也就是选项
  const isFirstArgOptions =
    Object.prototype.toString.call(times[0]) === "[object Object]";
  // @ts-ignore
  const {
    digits = 0, //
    colons = 1,
    base = 60,
    delimiter = ":",
    keep0 = true,
  }: To60sFromSecOptions = isFirstArgOptions ? times.shift() : {};
  let total = 0; // 总共有多少秒
  if (Object.prototype.toString.call(times) === "[object Array]") {
    for (const time of times) {
      // @ts-ignore
      total += time;
    }
  } else {
    total = +times;
  }
  const isMinus = total < 0;
  isMinus && (total = -total); // 如果为负数，则变为正数
  let arr = Array.from({ length: colons + 1 })
    .map((_, i) => i)
    .reverse()
    .map((i) => {
      if (i === 0) {
        return `${~~(total * 10 ** digits) / 10 ** digits}`;
      } else {
        const floored = Math.floor(total / base ** i);
        total %= base ** i;
        return floored + "";
      }
    });
  if (keep0) {
    arr = arr.map((e) => keep0s(e, `${~~base}`.length));
  }
  return `${isMinus ? "-" : ""}${arr.join(delimiter)}`;
}

/**
 * 计算60进制字符串的总和
 * @param options 选项，可以不填
 * @param args 60进制数字或字符串数组
 * @returns 返回计算后的数值
 */
export function sumOf60s(
  options?: To60sFromSecOptions | number | string,
  ...args: number[] | string[]
): string {
  let total: number = 0;
  // @ts-ignore
  const _strings: string[] = [options, ...args].filter(
    (e) => Object.prototype.toString.call(e) === "[object String]"
  ); // 筛选出字符串部分
  // @ts-ignore
  const _numbers: number[] = [options, ...args].filter(
    (e) => Object.prototype.toString.call(e) === "[object Number]"
  ); // 筛选出数字部分
  _numbers.forEach((e) => (total += e)); // 计算数字部分相加结果
  if (Object.prototype.toString.call(options) === "[object Object]") {
    // @ts-ignore 如果有options
    total += secOf60s(options, ..._strings);
    // @ts-ignore
    return to60sFromSec(options, total);
  } else {
    // @ts-ignore 如果不带options
    total += secOf60s(..._strings);
    return to60sFromSec(total);
  }
}

export interface GetColorByPercentageOptions {
  red?: [
    /**
     * 百分比为0时的red值
     */
    number,
    /**
     * 百分比为100时的red值
     */
    number
  ];
  green?: [
    /**
     * 百分比为0时的green值
     */
    number,
    /**
     * 百分比为100时的green值
     */
    number
  ];
  blue?: [
    /**
     * 百分比为0时的blue值
     */ number,
    /**
     * 百分比为100时的blues值
     */
    number
  ];
}

/**
 * 根据百分比线性计算颜色
 * @param percentage 百分比0-100之间
 * @param options 颜色计算参数
 * @returns
 */
export function getColorByPercentage(
  percentage: number,
  options: GetColorByPercentageOptions = {}
) {
  let _percentage0 = Math.min(100, Math.max(0, percentage));
  let { red = [0, 255], green = [240, 150], blue = [180, 101] } = options;
  const { max } = Math;
  const r =
    red[0] + ((red[1] - red[0]) * _percentage0) / max(_percentage0, 100);
  const g =
    green[0] + ((green[1] - green[0]) * _percentage0) / max(_percentage0, 100);
  const b =
    blue[0] + ((blue[1] - blue[0]) * _percentage0) / max(_percentage0, 100);
  return `rgb(${r}, ${g}, ${b})`;
}

/**
 * 将日期整理为指定格式的字符串
 * @param fmt 由Y(年), m(月), d(日), H(小时), M(分钟), S(秒) 和其他组成的字符串
 * @param date 时间或时间对象
 * @returns 返回日期
 */
export function dateFormat(
  fmt: string = "YY-mm-dd",
  date: Date | number | string = new Date()
): string {
  function _createOpt(date0: Date) {
    return {
      "[Yy]+": date0.getFullYear().toString(), // 年
      "m+": (date0.getMonth() + 1).toString(), // 月
      "d+": date0.getDate().toString(), // 日
      "H+": date0.getHours().toString(), // 时
      "M+": date0.getMinutes().toString(), // 分
      "S+": date0.getSeconds().toString(), // 秒
      // 有其他格式化字符需求可以继续添加，必须转化成字符串
    };
  }
  let opt: Object = _createOpt(date instanceof Date ? date : new Date(date));
  for (const k in opt) {
    let ret = new RegExp("(" + k + ")").exec(fmt);
    if (ret) {
      fmt = fmt.replace(
        ret[1],
        // @ts-ignore
        ret[1].length === 1 ? opt[k] : opt[k].padStart(ret[1].length, "0")
      );
    }
  }
  return fmt;
}

/**
 * 计算两个时间的月数差值
 * @param a 被减数
 * @param b 减数
 * @param useDigit 如果为是，则计算方法为每12个月365.25天，否则为月份差整数值
 * @returns {number}月份差值
 */
export function getMonthDifference(
  a: Date | string | number,
  b: Date | string | number,
  useDigit: boolean = false
): number {
  let _compare: Date[] = [new Date(), new Date()];
  if (a instanceof Date) {
    _compare[0] = a;
  } else {
    _compare[0] = new Date(a);
  }
  if (b instanceof Date) {
    _compare[1] = b;
  } else {
    _compare[1] = new Date(b);
  }
  if (useDigit) {
    let aMonth: number = (1000 * 3600 * 24 * 365.25) / 12; // 一个月的时长
    return (_compare[0].getTime() - _compare[1].getTime()) / aMonth;
  } else {
    return (
      _compare[0].getFullYear() * 12 +
      _compare[0].getMonth() -
      _compare[1].getFullYear() * 12 -
      _compare[1].getMonth()
    );
  }
}

export interface TitleTransform {
  [/** 原来的的键 */ key: string]: /** 转换后的键 */ string;
}
export type ObjArrToCsvParseFunc = (
  /**
   * 值
   */
  value: any,
  /**
   * 键
   */
  key: string,
  /**
   * 在整个数组中的index
   */
  index: number
) => string;
/**
 * 将由Object组成的数组转换为数组组成的数组的Array
 * @param rows 数据源
 * @param titlesFrom 从第几行获取title （从0开始）
 * @param parser 格式转换器
 * @param withTitles 是否需要加上第一行titles
 * @param titlesDictionary titles的转换字典，数据会根据这个字典key的顺序来对"列"进行排序
 * @returns 返回csv格式的数组
 */
export function objArrToCsv<T extends Object>(
  rows: T[] = [],
  titlesFrom: number = 0,
  parser: ObjArrToCsvParseFunc = (value) => value || "",
  withTitles: boolean = true,
  titlesDictionary: TitleTransform = {}
): string[][] {
  if (rows[titlesFrom]) {
    let titles = [...Object.keys(rows[titlesFrom])];
    // 如果没有dictionary，那么创建一个dictionary
    titlesDictionary ||
      (titlesDictionary = (() => {
        let obj: TitleTransform = {};
        for (let key in titles) {
          obj[key] = key;
        }
        return obj;
      })());

    // 根据titles dictionary的key顺序强制重新排序
    titles = [...Object.keys(titlesDictionary)].filter(
      // @ts-ignore
      (e) => titlesDictionary[e] !== undefined //把titlesDictionary中没有的元素去掉
    );
    // 第一行
    const titlesTransformed = titles.map((e) => titlesDictionary[e]);
    let _result = [...rows].map((row, i) => {
      // @ts-ignore
      return titles.map((key) => parser(row[key], key, i)); // 每行按titles顺序输出数组
    });
    withTitles && _result.unshift(titlesTransformed); // 如果需要第一行，就加上第一行
    return _result;
  } else {
    return [];
  }
}

/**
 * 将csv字符串或者数组转换为Object组成的数组
 * @param csv Csv数组或整个csv文档的字符串
 * @param titleStart 哪个index为列标题行
 * @param dataStart 从哪个index开始为数据
 * @param splitter 每列的分隔符，默认为",
 * @param titlesDictionary 将原来的title变换到key的字典
 * @returns 返回ObjectArr
 */
export function csvToObjArr(
  csv: string[][] | string = [],
  titleStart: number = 0,
  dataStart: number = titleStart + 1,
  splitter: string | RegExp = ",",
  titlesDictionary: TitleTransform = {}
): Object[] {
  if (/String/i.test(Object.prototype.toString.call(csv))) {
    // @ts-ignore 如果是string，先转换成Array
    csv = [...csv.split(/\n\r*/g)].map((row) => [...row.split(splitter)]);
  }
  // @ts-ignore
  const titles: string[] = csv[titleStart];
  // @ts-ignore
  const spliced: Object[] = csv.slice(dataStart);
  // @ts-ignore
  return spliced.map((row) => {
    const obj = {};
    for (const i in row) {
      // i 为列
      // @ts-ignore
      let key = titlesDictionary[titles[i]]
        ? // @ts-ignore
          titlesDictionary[titles[i]]
        : // @ts-ignore
          titles[i];
      // @ts-ignore
      obj[key] = row[i];
    }
    return obj;
  });
}
export interface CsvToObjArrOptions {
  csv?: any[][] | string;
  titleStart?: number;
  dataStart?: number;
  splitter?: string | RegExp;
  titlesDictionary?: TitleTransform;
}

export function csvToObjArr1<T extends Object = Object>({
  csv = [],
  titleStart = 0,
  dataStart = titleStart + 1,
  splitter = ",",
  titlesDictionary = {},
}: CsvToObjArrOptions = {}): T[] {
  if (/String/i.test(Object.prototype.toString.call(csv))) {
    // @ts-ignore 如果是string，先转换成Array
    csv = [...csv.split(/\n\r*/g)].map((row) => [...row.split(splitter)]);
  }
  // @ts-ignore
  const titles: string[] = csv[titleStart];
  // @ts-ignore
  const spliced: Object[] = csv.slice(dataStart);
  // @ts-ignore
  return spliced.map((row) => {
    const obj = {};
    for (const i in row) {
      // i 为列
      // @ts-ignore
      let key = titlesDictionary[titles[i]]
        ? // @ts-ignore
          titlesDictionary[titles[i]]
        : // @ts-ignore
          titles[i];
      // @ts-ignore
      obj[key] = row[i];
    }
    return obj;
  });
}

/**
 * 根据正则表达式将字符串分割为按顺序组成的匹配、不匹配正则表达式的字符串的数组
 * @param str 字符串
 * @param reg 匹配的正则
 * @param allowEmpty 是否允许空字符串
 * @returns 返回匹配的结果
 */
export function separate(
  str: string,
  reg: RegExp | string,
  allowEmpty: boolean = true
): string[] {
  const arr = [];
  if (Object.prototype.toString.call(reg) === "[object String]") {
    let _str = str;
    let _targetReg = `${reg}`;
    while (_str) {
      const _index = _str.indexOf(_targetReg);
      if (_index === -1) {
        arr.push(_str.replace(" ", " ")); // 如果找不到
        break;
      } else if (_index === 0) {
        // abc012 //假设abc是reg
        // 0123 //indexes
        arr.push(_targetReg.replace(" ", " "));
        _str = _str.slice(_targetReg.length); // 从3(reg.length)截到最后
      } else {
        // 012abc //假设abc是reg
        // 0123 //indexes
        arr.push(_str.substring(0, _index)); // 从3(_index)截到最后
        _str = _str.substring(_index); // 从3(_index)截到最后
      }
    }
  } else {
    let _str = str;
    while (_str) {
      const _index = _str.search(reg);
      const _searched = (_str.match(reg) || [""])[0];
      if (_index === -1) {
        arr.push(_str.replace(" ", " "));
        break;
      } else if (_index === 0) {
        // abc012 //假设abc是_searched
        // 0123 //indexes
        arr.push(_searched.replace(" ", " "));
        _str = _str.slice(_searched.length); // 从3(reg.length)截到最后
      } else {
        // 012abc //假设abc是_searched
        // 0123 //indexes
        arr.push(_str.substring(0, _index)); // 从3(_index)截到最后
        _str = _str.substring(_index); // 从3(_index)截到最后
      }
    }
  }
  return allowEmpty ? arr.flat(Infinity) : arr.filter((ele) => ele !== "");
}

export namespace Js {
  export type Types =
    | "Object"
    | "String"
    | "Number"
    | "Array"
    | "Undefined"
    | "Null"
    | "Boolean"
    | "Set"
    | string;
  /**
   * 任何Iterable类型，包括Object, Set, Array
   */
  export type Iterable =
    | {
        [key: string | number]: any | never;
      }
    | any[]
    | Set<any>;
  /**
   * 当寻找该元素或者该函数的返回值为true时就不向深层继续寻找了
   */
  export type StopElement =
    | ((
        /** 遍历到的对象或数组 */
        obj: Object | Array<any> | Set<any>
      ) => boolean | void)
    | Object
    | Array<any>
    | Set<any>;
  /**
   * @link https://www.lodashjs.com/docs/lodash.set#_setobject-path-value
   * @description Route 向一个Object或者Array内部寻找时使用的键的数组, _.set函数中的path
   */
  export type Route = (string | number)[];

  /**
   * 当寻找该位置或者该函数的返回值为true时就不向深层继续寻找了
   */
  export type StopPositionEle =
    | Route
    | ((/** 遍历到的位置 */ position: Route) => boolean | void);
  /**
   * GetRootsOfObj函数的选项
   */
  export interface AnalyzeOptions {
    /**
     * 当寻找该位置或者该函数的返回值为true时就不向深层继续寻找了
     */
    endEles?: StopElement[];
    /**
     * 在遇到什么位置时就不往下找了 (考虑baseRoute)
     */
    endRoutes?: (string | number)[][];
    /**
     * 是否视Set为Array (Iterable)，默认为false
     */
    considerSetAsArray?: boolean;
    /**
     * 返回的routeStr分隔符 用来把route给join起来，默认为":""
     */
    separator?: ":" | string;
    /**
     * 是否考虑将源本身加入到结果中(.route为[])，默认为false
     * @description 注. 使用lodash的 .get(origin, route) 获得值为undefined
     */
    considerOrigin?: boolean;
    /**
     * 路径中的 "1" 之类的是否切换成number 1
     */
    useNumber?: boolean;
    /**
     * 在此之前已经存在的route
     */
    baseRoute?: Route;
  }
  /**
   * @description 计算两个元素是否相等时用到的函数组，返回值为false/void时被认为不相等
   */
  export type CompareFunction = (
    /**
     * obj0中的元素
     */
    a: any,
    /**
     * obj1中的元素
     */
    b: any,
    /**
     * obj1或者obj0遍历到某个根元素时的route时的route
     */
    route: Route
  ) => boolean | void;

  /**
   * @description 用来判断某个route或者值是否被ignore的，如果被ignore了返回true
   * @returns 如果被ignore了返回true
   */
  export type IgnoreFunc = (report: Report) => boolean | void;

  /**
   *
   */
  export interface Report {
    /**
     * 路径，用数组表示 (考虑baseRoute)
     * @link https://www.lodashjs.com/docs/lodash.set#_setobject-path-value 的path
     */
    route: Route;
    /**
     * 路径，用数组表示 (不考虑baseRoute)
     * @link https://www.lodashjs.com/docs/lodash.set#_setobject-path-value 的path
     */
    routeIn: Route;
    /**
     * 路径，用字符串表示
     * @link https://www.lodashjs.com/docs/lodash.set#_setobject-path-value 的path
     */
    path: string;
    /**
     * 在该route的值
     */
    value: any | never;
    /**
     * 类型 (字符串)
     */
    instance: Types;
    /**
     * 嵌套在几个Array里，不考虑源本身，不考虑baseRoute，因为无法计算
     */
    insideArray: number;
    /**
     * 从0开始的深度，也就是route的length (考虑baseRoute)
     */
    depth: number;
    /**
     * 是否为该route的最远端
     */
    isEnd: boolean;
  }

  export interface ObjectRouteReport {
    ends: Report[];
    noneEnds: Report[];
    arrays: Report[];
  }
  /**
   * fixObjArrByTemplate中的warnOptions
   */
  export interface WarnOptions {
    /**
     * 用来log错误
     */
    logger?: (
      /**
       * 在template中的原样
       */
      template: Report,
      /**
       * 实际的值
       */
      actual: Report
    ) => any | never;
  }
  /**
   * 在route中是否存在上级Array（不考虑源本身） (键为数字的上级)
   * @param routeIn
   * @returns 是否存在上级Array
   */
  export function hasArrBefore(routeIn: Route, origin: Object): number {
    let count = 0;
    routeIn.forEach((_e, i) => {
      if (i < routeIn.length) {
        let _tempRoute = routeIn.slice(0, i);
        // 如果i不是最后一位
        if (/Array/.test(getInstance(_.get(origin, _tempRoute)))) {
          count++;
        }
      }
    });
    return count;
  }
  /**
   * 用来默认判断两个值是否相等的函数
   * @param a 值a
   * @param b 值b
   * @returns a和b是否相等
   */
  export const defaultCompareFunction = (a: any, b: any): boolean =>
    areTheSame(a, b);
  /**
   * @description 根据route获得path
   */
  export function toRoutePath(route: Route, separator = "."): string {
    let str = "";
    for (let ele of route) {
      if (isNaN(+ele)) {
        str += separator + ele;
      } else {
        str += `[${+ele}]`;
      }
    }
    if (str.indexOf(separator) === 0) {
      str = str.substring(1);
    }
    return str;
  }
}

/**
 * 判断一个元素是否为Array Object Set
 * @param obj 任意字面量
 * @returns 是否
 */
export function isIterable(obj: any): boolean {
  return /Array|Object|Set/.test(Object.prototype.toString.call(obj));
}

/**
 * 获取一个Object、或Array的全部path，可用于lodash的has
 * @link https://www.lodashjs.com/docs/lodash.has
 * @param origin 要分析的对象
 * @param param1 选项
 * @returns 每个path和其值和类型
 */
export function analyzeObject(
  origin: Js.Iterable,
  {
    considerOrigin = false,
    endEles = [],
    endRoutes = [],
    considerSetAsArray = false,
    separator = ".",
    useNumber = true,
    baseRoute = [],
  }: Js.AnalyzeOptions = {}
): Js.Report[] {
  /** 一个到底的route */
  let reports: Js.Report[] = considerOrigin
    ? [
        {
          route: baseRoute,
          routeIn: [],
          value: origin,
          path: Js.toRoutePath(baseRoute, separator),
          instance: getInstance(origin),
          insideArray: 0,
          depth: 0,
          isEnd: false,
        },
      ]
    : [];
  /** 判断函数是否被忽略 */
  let _testIsEnd = (_obj: any, _position: Js.Route): boolean => {
    for (let _stopEle of endEles) {
      if (_stopEle instanceof Function) {
        if (_stopEle(_obj)) return true;
      } else if (areTheSame(_obj, _stopEle)) {
        return true;
      }
    }
    for (let _stopPos of endRoutes) {
      if (_stopPos instanceof Function) {
        if (_stopPos(_position)) return true;
      } else {
        if (areTheSame(_stopPos, _position)) {
          return true;
        }
      }
    }
    return false;
  };
  let _setRouteFormat = (_routeElement: string | number) => {
    if (useNumber) {
      let _tryNumber = +_routeElement;
      if (isNaN(_tryNumber)) {
        return _routeElement;
      } else {
        return _tryNumber;
      }
    } else {
      return _routeElement + "";
    }
  };
  /** 用来寻找route的迭代函数 */
  let _looper = (_objLoop: Js.Iterable, objRoute: (string | number)[] = []) => {
    for (let i in _objLoop) {
      // @ts-ignore
      let value: any = _objLoop[i];
      /** 用于计算的route */
      let routeIn: Js.Route = [...objRoute, i].map(_setRouteFormat);
      /** 用于出结果的和判断是否被ignored的route */
      let route = [...baseRoute, ...routeIn].map(_setRouteFormat);
      let insideArray = Js.hasArrBefore(routeIn, origin);
      /** 当前量是否为 Iterable */
      let _isThisIterable = isIterable(value);
      /** 当前位置是否被设置为end */
      let _isThisEnd = _testIsEnd(value, route);
      /** 需要push的Report */
      let _toPush: Js.Report = {
        route,
        routeIn,
        value,
        insideArray,
        path: Js.toRoutePath(route, separator),
        instance: getInstance(value),
        depth: route.length,
        isEnd: false,
      };
      // 修改 _toPush 的参数
      if (_isThisEnd || !_isThisIterable) {
        _toPush.isEnd = true;
      } else {
        let _type = getInstance(value);
        if (/Set/.test(_type)) {
          if (considerSetAsArray) {
            _looper(Array.from(value), routeIn);
          } else {
            _toPush.isEnd = true;
          }
        } else {
          _looper(value, routeIn);
        }
      }
      reports.push(_toPush);
    }
    return reports;
  };
  if (!isIterable(origin)) {
    return reports;
  } else {
    return _looper(origin);
  }
}

/**
 * 计算两个字面量的每个路径值对应值是否相同
 * @example
 * let compare =  [
 *     {a: 1, b: 2, c: [{ d:"3", e:"4"}] },
 *     {b: 2, a: 1, c: [{ e:"4", d:"3"}] }
 * ]
 * console.log(areTheSame(compare[0], compare[1])) // true
 * @param obj0 第一个object
 * @param obj1 第二个object
 * @param options 调用objectRootsDeep算法时的options
 * @param compares 默认用JSON.stringify来对比 对比结果如果为false则识别为不同
 * @returns
 */
export function areTheSame(
  obj0: Js.Iterable,
  obj1: Js.Iterable,
  options: Js.AnalyzeOptions = {},
  compares: Js.CompareFunction[] = [Js.defaultCompareFunction]
) {
  let { stringify: toStr } = JSON;
  if (!isIterable(obj0) && !isIterable(obj1)) {
    // 若果两者都不是可循环量，直接用JSON.stringify判断
    return toStr(obj0) === toStr(obj1);
  }
  let result0 = analyzeObject(obj0, options);
  let result1 = analyzeObject(obj1, options);
  let routes0: string[] = result0
    .filter((e) => e.isEnd)
    .map((e) => toStr(e.routeIn));
  let routes1: string[] = result1
    .filter((e) => e.isEnd)
    .map((e) => toStr(e.routeIn));
  if (routes0.length === routes1.length) {
    for (let i in routes0) {
      if (!routes1.includes(routes0[i])) {
        // 如果某个路径在另一个中不存在
        return false;
      } else {
        let { routeIn } = result0[i];
        for (let compare of compares) {
          let [v0, v1] = [_.get(obj0, routeIn), _.get(obj1, routeIn)];
          if (!compare(v0, v1, routeIn)) {
            // 如果对比结果为false
            return false;
          }
        }
      }
    }
    return true;
  } else {
    return false;
  }
}

/**
 * 判断一个Object是否为Array Like。
 * Array和Set会被认为是Array Like。
 * 如果Object是 { } 的被认为不是 ArrayLike。
 * 如果Object有length、或\d的key，在strict时只要没有其他的key会被被认为是Array Like, 如果有的话，只有在不strict时被认为是ArrayLike。
 * @param source 数据源，如果不是Object或者Array或者Set，会直接返回false
 * @param strict (默认为true) 是否对Object启用严格模式。如果strict，必须所有的key都是 \d或者length。不strict时只要有1个键是\d或者length即认为是ArrayLike
 * @returns 根据上述规则的值
 */
export function arrayLike<T extends Js.Iterable>(
  source: T,
  strict: boolean = true
): any[] | false {
  if (/Array|Set/.test(getInstance(source))) {
    // @ts-ignore
    return Array.from(source);
  } else if (isIterable(source)) {
    let _arr: T[] = [];
    let _hasArrayLikeKeys = false;
    /** 是否使用index来构建数组 */
    for (let key in source) {
      if (/^(\d+|length)$/.test(key)) {
        // @ts-ignore
        _arr[key] = source[key];
        _hasArrayLikeKeys = false;
      } else {
        if (strict) {
          return false; // 如果任意的key不是不是数字类型
        }
      }
      if (_hasArrayLikeKeys) {
        // 经过全部筛选，全部都是arrayLikeKeys
        // 如果strict的话，早就return false了
        return _arr;
      } else {
        return false;
      }
    }
    // 如果force 或者 source为{}
    return _arr;
  } else {
    // 如果根本就是不是Object、Array或者Set
    return false;
  }
}

/**
 * 修改一个Object路径中所有含有key为数字或者length的对象为Array
 * @param origin 数据源
 * @param ignores 忽略的key
 * @returns
 */
export function fixArrayLikesDeep<T extends Js.Iterable>(
  origin: T,
  ignores: Js.IgnoreFunc[] = []
): T {
  if (!isIterable(origin)) {
    return origin;
  } else {
    let _obj: Object = {};
    let _isOriginArrLike = arrayLike(origin);
    if (_isOriginArrLike) {
      _obj = _isOriginArrLike;
    } else {
      _obj = origin;
    }
    analyzeObject(_obj, { considerSetAsArray: true })
      .sort((a, b) => a.depth - b.depth)
      .forEach((e) => {
        let { value, routeIn } = e;
        let _isIgnored = false;
        let _isArrayLike = arrayLike(value);
        for (let item of ignores) {
          if (item(e)) {
            _isIgnored = true;
            break;
          }
        }
        if (_isArrayLike && !_isIgnored) {
          _.set(_obj, routeIn, _isArrayLike);
        }
      });
    //@ts-ignore
    return _obj;
  }
}

/**
 * 根据template修复row中所有数据(deep)
 * @param rowTemplate row的模板
 * @param row 需要修改的模板
 * @param options 同analyzeObject的options
 * @param onFix 当出现改动时的options
 * @returns 修复好的row，row的原值也会被更改
 */
export function fixObjArrByTemplate<T extends Object>(
  rowTemplate: T,
  row: Object,
  options: Js.AnalyzeOptions = {},
  onFix: Js.WarnOptions = {}
): T {
  let { logger = () => 0 } = onFix;
  let _formActualReport = (_target: any) => ({
    value: _target,
    instance: getInstance(_target),
  });
  // @ts-ignore
  if (!/Object/.test(getInstance(row))) {
    // to log ---------------
    let route = options.baseRoute || [];
    let path = Js.toRoutePath(route, options.separator || ".");
    let _templateReport = {
      route,
      routeIn: [],
      path,
      value: rowTemplate,
      instance: getInstance(rowTemplate),
      insideArray: 0,
      depth: route.length,
      isEnd: false,
    };
    logger(_templateReport, {
      ..._templateReport,
      ..._formActualReport(row),
    });
    // fix ---------------
    row = {};
  }
  analyzeObject(rowTemplate, options)
    .filter((e) => e.insideArray < 1) // 仅筛选是Array或Array之前的路径
    .forEach((e) => {
      let { routeIn, value, instance } = e;
      /** row在该route的value */
      let _rowValueAtRoute = _.get(row, routeIn);
      if (_rowValueAtRoute === undefined) {
        // to log ---------------
        logger(e, { ...e, ..._formActualReport(_rowValueAtRoute) });
        // fix ---------------
        // 如果该位置根本没有value
        _.set(row, routeIn, value); // 则设置为模板的默认值
      }
      if (/Array/.test(instance)) {
        // @ts-ignore
        if (!/Array/.test(getInstance(_rowValueAtRoute))) {
          // to log ---------------
          logger(e, { ...e, ..._formActualReport(_rowValueAtRoute) });
          // 如果本来在该位置应该是个Array, 但实际上不是
          let _test = arrayLike(_rowValueAtRoute); // 尝试修复t
          try {
            _.set(row, routeIn, _test || Array.from(_rowValueAtRoute));
          } catch (error) {
            _.set(row, routeIn, []);
          }
          _rowValueAtRoute = instance; // 如果template是Iterable但row不是
        }
        let _newTemplate = value[0];
        if (/Object/.test(getInstance(_newTemplate))) {
          // 如果在模板上，该位置是个Array并且存在Object模板
          for (let i in _rowValueAtRoute) {
            // 对于row的每行内容重新进行循环
            // @ts-ignore
            fixObjArrByTemplate(
              _newTemplate,
              _rowValueAtRoute[i],
              {
                ...options,
                baseRoute: [...e.route, i],
              },
              onFix
            );
          }
        }
      }
    });
  // @ts-ignore
  return row;
}

/**
 * @description Object.assign 的改进版，深度赋值
 * @example let a = { a: { a:"1" } };
 * let b = { a: { b:"1" } };
 * objectAssignDeep(a, b);
 * console.log(a) // { a: { a: '1', b: '2' } }
 * @param target Object.assign的第1个参数
 * @param source Object.assign的第2个参数
 * @param cloneTarget 是否clone target以防止修改target本身
 * @returns 修改后的target
 */
export function objectAssignDeep<T extends Object, U extends Object>(
  target: T,
  source: U,
  cloneTarget: boolean = false
): T & U {
  let target1 = cloneTarget ? _.cloneDeep(target) : target;
  for (let { route, value, isEnd } of analyzeObject(source)) {
    isEnd && _.set(target1, route, value);
  }
  // @ts-ignore
  return target1;
}

/**
 * 计算date所在月的1号
 * @param date
 * @returns date所在月的1号
 */
export function get1stDayOfMonth(
  date: string | number | Date = new Date()
): Date {
  return new Date(dateFormat("yyyy-mm-01", date));
}

/**
 * 计算date所在的下个月的1号
 * @param date
 * @returns date所在月的下一个月
 */
export function get1stDayOfNextMonth(
  date: string | number | Date = new Date()
): Date {
  let reg = /^(?<year>\d+)-(?<month>\d+)/;
  let {
    // @ts-ignore
    groups: { year, month },
  } = reg.exec(dateFormat("yyyy-mm", date));
  year = +year;
  month = +month;
  if (month >= 12) {
    year++;
    month = 1;
  } else {
    month++;
  }
  return new Date(`${year}/${month}/1`);
}

/**
 * 计算当前月的最后一天
 * @param date
 * @returns date所在月的最后一天
 */
export function getLastDayOfMonth(
  date: string | number | Date = new Date()
): Date {
  return new Date(get1stDayOfNextMonth(date).getTime() - 24 * 3600 * 1000);
}

/**
 * 计算两个date之间的所有月的1号的Date组成的数组
 * @param date0
 * @param date1
 * @returns 获得两个date之间的所有months数组
 */
export function getMonthsBetween(
  date0: string | number | Date,
  date1: string | number | Date
): Date[] {
  if (!(date0 instanceof Date)) {
    date0 = new Date(date0);
  }
  if (!(date1 instanceof Date)) {
    date1 = new Date(date1);
  }
  let fixedOrder = _.orderBy([date0, date1], (e) => e.getTime());
  let arr = [];
  for (
    let time = fixedOrder[0].getTime(); // 从较小数字开始
    time <= fixedOrder[1].getTime(); // 到较大数字结束
    time += 28 * 24 * 3600 * 1000 // 以28天为间隔
  ) {
    arr.push(time);
  }
  arr = arr.map((e) => dateFormat("yyyy-mm-01", e));
  return Array.from(new Set(arr)).map((e) => new Date(e));
}

export namespace MathExtra {
  /**
   * 根据base进行幂运算
   * @example
   * let a = log(2**5 , 2)
   * console.log(a) // 5
   * @param num 数字
   * @param base 基数
   * @returns
   */
  export function log(num: number, base: number): number {
    return Math.log(num) / Math.log(base);
  }
}

export namespace Excel {
  let emptyReg = /[\n\r\s]/g;
  /**
   * 把数字转换成列标
   * @example numToCol(1)) // "A"
   * @example numToCol(26)) // "Z"
   * @example numToCol(30)) // "AD"
   * @param num 从1开始的数字 (注意：从1开始)
   * @returns 1=>"A" 26->Z -> 30->AD
   */
  export function numToCol(num: number): string {
    let { log: log0 } = MathExtra;
    let howMany = Math.floor(log0(num, 26));
    let str: string[] = [];
    for (let i = howMany; i >= 0; i--) {
      let int = ~~(num / 26 ** i);
      num %= 26 ** i;
      str.push(String.fromCharCode(64 + int));
    }
    return str.join("");
  }

  /**
   * 把列转换成数字 (注意：从1开始)
   * @example colToNum("A") // 1
   * @example colToNum("z") // 26
   * @example colToNum("AD") // 30
   * @param col "A"或者"ABC"不要有空格，可以支持大写或小写
   * @returns 1->A
   */
  export function colToNum(col: string): number {
    col = col.toLocaleUpperCase().replace(emptyReg, "");
    let len = col.length;
    let total = 0;
    for (let i = 0; i < col.length; i++) {
      total += (col.charCodeAt(i) - 64) * 26 ** (len - i - 1);
    }
    return total;
  }

  /**
   * 根据行号列号计算位置
   * @example xyToPos(5, 6) // "E6"
   * @example xyToPos(30, "D") // "AD4"
   * @param x 列号(数字或字母，从1开始的)
   * @param y 行号(数字或字母，从1开始的)
   * @returns 返回正确的行列位置
   */
  export function xyToCell(x: number | string, y: number | string): Position {
    let x0 = (x + "").replace(emptyReg, "");
    let y0 = (y + "").replace(emptyReg, "");
    if (/^\d+$/.test(x0)) {
      // x是数字
      x0 = numToCol(+x0);
    }
    if (!/^\d+$/.test(y0)) {
      // y不是数字
      y0 = colToNum(y0) + "";
    }
    // x0不是数字，y0是数字
    return { x: colToNum(x0), y: +y0, cell: `${x0}${y0}`.toLocaleUpperCase() };
  }

  /**
   * 根据字符串获取单元格位置参数
   * @example cellToXy("E6") // {x: 5, y: 6, cell:"E6" }
   * @param cell 单元格位置
   */
  export function cellToXy(cell: string): Position {
    cell = cell.toLocaleUpperCase().replace(emptyReg, "");
    let y = cell.replace(/[A-Z]+/g, ""); // 只要数字
    let x = cell.replace(/\d+/g, ""); // 只要字符
    return {
      x: colToNum(x),
      y: +y,
      cell: xyToCell(x, y).cell,
    };
  }

  /**
   * 根据字符串或者行列数获取单元格位置
   * @param pos 单元格位置
   * @returns
   */
  export function parsePos(pos: RawInputPos): Position {
    if ("String" === getInstance(pos)) {
      // @ts-ignore
      return cellToXy(pos);
    } else {
      // @ts-ignore
      return xyToCell(pos.x, pos.y);
    }
  }

  /**
   * 计算单元格位置的移动x,y后的位置
   * @param pos 单元格位置
   * @param x 列移动，可以为负数，0表示不移动
   * @param y 行移动，可以为负数，0表示不移动
   * @returns 移动后的位置
   */
  export function toPosition(pos: RawInputPos, x: number, y: number): Position {
    let pos0 = parsePos(pos);
    pos0.x += x;
    pos0.y += y;
    return parsePos({ x, y });
  }
  export interface Position {
    x: number;
    y: number;
    cell: string;
    sheet?: string;
  }

  export type RawInputPos = { x: string | number; y: string | number } | string;

  export type ExcelData =
    | { [key: string]: (string | number | never)[] }
    | (string | number | never)[][]
    | (string | number | never);

  export interface Distribution {
    /**
     * x列的实际位置，从1开始
     */
    x: number;
    /**
     * 用于哪个sheet
     */
    sheet: string;
    /**
     * 该位置的value
     */
    value: any;
    /**
     * y列的实际位置，从1开始
     */
    y: number;
    /**
     * cell位置
     * @example "A2"; "AC4";
     */
    cell: string;
    /**
     * 在源数组中的位置
     */
    index: {
      /**
       * 列位置从0开始
       */
      x: number;
      /**
       * 行位置从0开始
       */
      y: number;
    };
  }

  export type Parser = (target: Distribution) => any | never;

  /**
   * 将Data转换为Excel写入任务
   * @param position 写入的cell的位置
   * @param sheet 要写入哪个sheet
   * @param data 要写入的data
   * @param parser 将data转换为数字或字符串的函数
   * @returns 返回写入Excel的任务分布
   */
  export function distribute(
    position: RawInputPos | string,
    sheet: string,
    data: ExcelData,
    parser: Parser = (e) => e.value + ""
  ): Distribution[] {
    // 参考位置
    let _refPos: Position = parsePos(position);
    let arr = [];
    if (getInstance(data) === "Array") {
      //@ts-ignore
      for (let yPlus in data) {
        //@ts-ignore
        for (let xPlus in data[yPlus]) {
          let thisPos = parsePos({
            x: +xPlus + _refPos.x,
            y: +yPlus + _refPos.y,
          });
          //@ts-ignore
          let value = data[yPlus][xPlus];
          let toPush: Distribution = {
            ...thisPos,
            sheet,
            value,
            index: {
              x: +xPlus,
              y: +yPlus,
            },
          };
          toPush.value = parser(toPush);
          arr.push(toPush);
        }
      }
    } else {
      let toPush: Distribution = {
        ..._refPos,
        sheet,
        value: data,
        index: {
          x: 0,
          y: 0,
        },
      };
      toPush.value = parser(toPush);
      arr.push(toPush);
    }
    return arr;
  }
}

/**
 * csv相关无需依赖的函数和接口
 */
export namespace CSV {
  /**
   * 键为非整数，值为该列该位置对应的值
   */
  export interface ColIndexed {
    [key: number]: number | string;
  }

  /** 第一层键为列名称，第二层键为非整数 */
  export interface Indexed {
    [key: string]: ColIndexed;
  }

  export interface InterpolateFuncParam {
    /** 第0个线性点的x坐标 */
    x0: number;
    /** 第0个线性点的y坐标 */
    y0: any;
    /** 第1个线性点的x坐标 */
    x1: number;
    /** 第1个线性点的y坐标 */
    y1: any;
  }

  export interface InterpolateFuncArg extends InterpolateFuncParam {
    /** 实际的x */
    x: number;
    /** 列名称 */
    colName?: string;
  }
  export type InterpolateFunc = (arg: InterpolateFuncArg) => any;

  export namespace StartInterpolation {
    export interface Option {
      /** 所有秒数的数组 */
      allFrames: number[];
      /** 类似于 { col0: { 0 : 1, 0.25: 4} } 这样组成的字典，第一层键为列名称，第二层键为非整数数字 */
      indexed: Indexed;
      /** 用于计算插值的函数 */
      interpolate?: InterpolateFunc;
    }
  }

  export namespace GetIndexed {
    export interface Option {
      /**
       * 由Object组成的数组，其中Object的每个值都是数组，这样可以处理一行同列名多项数据
       */
      data: { [key: string]: (string | number)[] }[];
      /**
       * 每个标题都分布在那些列
       */
      titleSet: { [key: string]: number[] };
      /**
       * 第0个index在总数组中的index
       */
      starting?: number;
    }
  }

  export namespace GetCsvParams {
    export interface Option {
      /** 读取的csv文件内容 */
      csvStr: string;
      /** 哪个index为titles (从0开始计算，默认为0) */
      titleIndex?: number;
      /** 哪个index为content的第一行 (从0开始计算，默认为2) */
      contentStart?: number;
      /** 每行用什么分割，默认为 \r?\n */
      rowSplitter?: string | RegExp;
      /** 每行用什么分割，默认为 "," */
      colSplitter?: string | RegExp;
    }
    export interface Result extends GetIndexed.Option {
      /**
       * 某个列名的最大重复次数的倒数
       * 也就是一行的某个参数最大有多少帧
       */
      minInterval: number;
      /**
       * 一共都有哪些列标题(已去重)
       */
      availableTitles: string[];
      /** csv的行数 */
      len: number;
    }
  }

  export namespace CsvInterpolateSync {
    export interface Option extends GetCsvParams.Option {
      /** 插值函数, 仅在Sync中可选 */
      interpolate?: InterpolateFunc;
      /** 使用多少个process来处理，默认和cpu总数相等,仅异步时使用 */
      // processLen?: number;
      // /** 是否使用异步处理 (多进程处理)，默认为true */
      // useAsync?: boolean;
    }
  }
  export namespace GetNearest2 {
    export interface ResultEmpty {}
    export interface Result0 {
      /** 最近元素的键 */
      key0: number;
      /** 最近元素的值 */
      value0: string | number;
    }
    export interface Result1 extends Result0 {
      /** 第二近元素的键(可能不存在) */
      key1: number;
      /** 第二近元素的值(可能不存在) */
      value1: string | number;
    }
  }

  export namespace FinalResult {
    export interface Interpolated {
      [key: string]: boolean;
    }
    export interface Row {
      [key: string]: number | string | Interpolated;
      /** 都有哪些列是被interpolated的，而不是原值 */
      $interpolated: Interpolated;
    }
  }

  export function round(num: number, digits: number = 10) {
    return Math.round(num * Math.pow(10, digits)) / Math.pow(10, digits);
  }

  const interpolateTime: InterpolateFunc = ({ x0, y0, x1, y1, x }) => {
    // let matched = y0.match(":").length;
    let diff = x1 - x0;
    y0 = secOf60s(y0);
    y1 = secOf60s(y1);
    let k = (y1 - y0) / diff;
    let b = y0 - k * x0;
    let y = k * x + b;
    return sumOf60s({ colons: 2, digits: 5 }, y);
  };

  const interpolateLR: InterpolateFunc = ({ x0, y0, x1, y1, x }) => {
    // let matched = y0.match(":").length;
    let diff = x1 - x0;
    let isNegative = [/L/i.test(y0), /L/i.test(y1)];
    y0 = y0.replace(/[LR]\s*/g, "");
    y1 = y1.replace(/[LR]\s*/g, "");
    isNegative[0] && (y0 = -y0);
    isNegative[1] && (y1 = -y1);
    let k = (y1 - y0) / diff;
    let b = y0 - k * x0;
    let y = round(k * x + b);
    let { abs } = Math;
    return (y < 0 ? "L" : "R") + (abs(y) + "");
  };

  /**
   * @param x0 第0个线性点的x坐标
   * @param y0 第0个线性点的y坐标
   * @param x1 第1个线性点的x坐标
   * @param y1 第1个线性点的y坐标
   * @param x 实际的x
   * @returns 计算出来的结果
   */
  export const defaultInterpolate: InterpolateFunc = (arg) => {
    let { x0, y0, x1, y1, x } = arg;
    if (x0 === x1) {
      return y0;
    } else {
      let diff = x1 - x0;
      if (isNaN(+y0) || isNaN(+y1)) {
        if (/^(\d+:\d*)+$/.test(y0)) {
          return interpolateTime({ x0, y0, x1, y1, x });
        } else if (/^[LR]\s*[0-9.]+$/.test(y0)) {
          return interpolateLR({ x0, y0, x1, y1, x });
        } else if (Math.abs(x0 - x) <= Math.abs(x1 - x)) {
          // 如果离index0更近
          return y0;
        } else {
          // 如果离index1更近
          return y1;
        }
      } else {
        let k = (y1 - y0) / diff;
        let b = y0 - k * x0;
        return round(k * x + b);
      }
    }
  };

  /**
   * 将一段allFrames计算出来的数据转换成一个线性插值好的数组，
   * 主要用于子线程中
   * @param option 选项
   */
  export function interpolateRowsSync({
    allFrames,
    indexed: colDataWithIntervals,
    interpolate = defaultInterpolate,
  }: StartInterpolation.Option): FinalResult.Row[] {
    let finalData: FinalResult.Row[] = [];
    for (let i of allFrames) {
      let thisRow: FinalResult.Row = { $interpolated: {} };
      for (let colName in colDataWithIntervals) {
        let colData = colDataWithIntervals[colName];
        let nearest2 = getNearest2(colData, i);
        if (+nearest2.x0 !== +i) {
          // 说明是线性插值得来的
          thisRow.$interpolated[colName] = true;
        }
        thisRow[colName] = interpolate({ ...nearest2, x: i });
      }
      finalData.push(thisRow);
    }
    return finalData;
  }

  /**
   * 初步读取csv文件参数和数据
   * @param options 读取设置
   * @returns 读取结果
   */
  export function getCsvParams(
    options: GetCsvParams.Option
  ): GetCsvParams.Result {
    let {
      csvStr,
      titleIndex = 0,
      contentStart = 2,
      rowSplitter = /\r?\n/g,
      colSplitter = ",",
    } = options;
    let csvRows: string[][] = csvStr
      .split(rowSplitter)
      .map((e) => e.split(colSplitter));
    /** 原title那行 */
    let titleRowRaw = csvRows[titleIndex].map((e) => e.trim());
    /** 已去重的全部titles */
    let availableTitles = Array.from(new Set(titleRowRaw)).filter((e) => e);
    /** 对于每种titles都有哪些列号 */
    let titleSet: { [key: string]: number[] } = {};
    for (let title of availableTitles) {
      if (!titleSet[title]) titleSet[title] = [];
    }
    for (let index in titleRowRaw) {
      let title = titleRowRaw[index];
      titleSet[title].push(+index);
    }
    /** 多少秒1帧 */
    let minInterval =
      1 /
      Math.max(
        ...Object.values(titleSet)
          .filter((e) => e)
          .map((e) => e.length),
        1
      );
    /** 原content那些行 */
    let contentRowsRaw = csvRows.slice(contentStart);
    /** 初步转化后的contentRows */
    let data = [];
    for (let i in contentRowsRaw) {
      let row = contentRowsRaw[i];
      let rowSet: { [key: string]: (string | number)[] } = {};
      let isRowValid = true;
      for (let colName in titleSet) {
        let _tileSet = titleSet[colName];
        // let { length: _len } = _tileSet;
        rowSet[colName] = _tileSet.map((e) => {
          let value: number | string = row[e];
          if (row[e] === undefined) isRowValid = false;
          else if ("string" === typeof value) value = value.trim();
          return value;
        });
      }
      if (isRowValid) data.push(rowSet);
      else break;
    }
    return { minInterval, titleSet, availableTitles, data, len: data.length };
  }

  /**
   * 将csv每列的有效帧记录为Object
   * @param option 可以直接使用getCsvParams的返回值
   * @param starting 第0个index在总数组中的index
   * @returns 第一层键为列名称，第二层键为非整数
   */
  export function getIndexedSync(option: GetIndexed.Option): Indexed {
    let { data, titleSet, starting = 0 } = option;
    let indexed: Indexed = {};
    for (let i in data) {
      for (let colName in data[i]) {
        let { length: len } = titleSet[colName];
        for (let j in data[i][colName]) {
          let index: number = +j / len + +i + +starting;
          if (!indexed[colName]) indexed[colName] = {};
          indexed[colName][index] = data[i][colName][j];
        }
      }
    }
    return indexed;
  }

  /**
   *
   * @param col 列的indexed数据
   * @param index 要查询的index的数据
   * @returns 最近的两个键和值
   */
  export function getNearest2(
    col: ColIndexed,
    index: number
  ): InterpolateFuncParam {
    let { abs } = Math;
    let keys = Object.keys(col)
      .sort((a, b) => abs(+a - +index) - abs(+b - +index))
      .filter((e) => col[+e] !== "");
    return {
      x0: +keys[0] || 0,
      y0: col[+keys[0]] || "",
      x1: +keys[1] || 0,
      y1: col[+keys[1]],
    };
  }

  /**
   * 获取计算后应该显示的帧数
   * @param csvParams
   * @returns
   */
  export function getAllFrames(csvParams: GetCsvParams.Result): number[] {
    let { minInterval, data } = csvParams;
    let arr = [];
    for (let frame = 0; frame < data.length; frame += minInterval) {
      arr.push(frame);
    }
    return arr;
  }

  /**
   * 读取.csv文件并转化为帧数据 (同步)
   * @param option 设置
   * @returns 读取结果
   */
  export function csvInterpolateSync(
    option: CsvInterpolateSync.Option
  ): FinalResult.Row[] {
    let { interpolate = defaultInterpolate } = option;
    let csvParams = getCsvParams(option);
    let indexed = getIndexedSync(csvParams);
    let allFrames = getAllFrames(csvParams);
    let result = interpolateRowsSync({ indexed, allFrames, interpolate });
    return result;
  }
}

/**
 * Element-Plus常用的一些算法
 */
export namespace Element {
  export interface ElementFilter<T> {
    text: T;
    value: T;
  }
  export type ElementFilters<T extends Object> = {
    [key in keyof T]: ElementFilter<T[key]>[];
  };

  /**
   * 生成计算平均值或者总值的函数, 用于绑定el-table的summary-method
   * @link https://element-plus.org/zh-CN/component/table.html#table-%E5%B1%9E%E6%80%A7
   * @param option 选项
   * @returns
   */
  export function summaryMethod({
    useAverage = false,
    na = "N/A",
    zero = "",
    format = (e: number | string) => `${~~(+e * 10) / 10}`,
    averageFilter = (e: any) => !isNaN(+e) && (e === 0 || e === "0" || e),
  }: {
    /** 是否使用平均值 */
    useAverage?: boolean;
    /** 如果遇到N/A（本行有普通字符串），所显示的内容 */
    na?: "N/A";
    /** 如果遇到0，所显示的内容 */
    zero?: "";
    /** 根据计算出的内容，具体要显示出的内容 */
    format?: (
      /** 计算出的sum,包括已定义的na和zero */
      sum: number | string,
      /** 该行的propertyName */
      property: string
    ) => string | number;
    /** 用于判断函数的 */
    averageFilter?: (
      /** 该cell的value */
      value: any,
      /** 该位置的column名称(Object的propertyName) */
      columnName: string
    ) => any;
  }): ({
    columns,
    data,
  }: {
    columns: { property: string }[];
    data: { [key: string]: any }[];
  }) => { [key: string]: string | number; [key: number]: string | number } {
    return function ({
      columns,
      data,
    }: {
      columns: { property: string }[];
      data: { [key: string]: any }[];
    }): { [key: number | string]: number | string } {
      const sums: string[] = [];
      columns.forEach((column, index) => {
        if (index === 0) {
          sums[index] = "Total Cost";
          return;
        }
        const values = data.map((item) => Number(item[column.property]));
        if (!values.every((value) => Number.isNaN(value))) {
          /** 总计值 */
          let sumValue = values.reduce((prev, curr) => {
            const value = Number(curr);
            if (!Number.isNaN(value)) {
              return prev + curr;
            } else {
              return prev;
            }
          }, 0);
          if (useAverage) {
            let count: number = values.filter((value) =>
              averageFilter(value, column.property)
            ).length;
            // @ts-ignore
            sums[index] = sumValue / count || zero;
          } else {
            // @ts-ignore
            sums[index] = sumValue || zero;
          }
          // @ts-ignore
          sums[index] = format(sums[index], column.property);
        } else {
          sums[index] = na;
          // @ts-ignore
          sums[index] = format(sums[index], column.property);
        }
      });
      // @ts-ignore
      return sums;
    };
  }

  /**
   * 生成el-table的filters属性的函数
   * @link https://element-plus.org/zh-CN/component/table.html#table-%E5%B1%9E%E6%80%A7
   * @param tableData
   * @returns 返回值可用于element-ui的el-table的filters
   */
  export function generalFilters<T extends Object>(
    tableData: T[]
  ): ElementFilters<T> {
    // @ts-ignore
    let obj: ElementFilters<T> = {};
    if (!tableData[0]) {
      // @ts-ignore
      return {};
    }
    for (let key in tableData[0]) {
      let values = tableData
        .map((e) => e[key])
        .filter((e) => getInstance(e) === "String");
      values = Array.from(new Set(values));
      // @ts-ignore
      obj[key] = values
        .filter((e) => e)
        .map(
          (e): ElementFilter<T> =>
            // @ts-ignore
            ({ text: e, value: e })
        );
    }
    return obj;
  }

  /**
   * 用于element-ui的el-table的filter-method (直接绑定就可以)
   * @description 直接用就可以了
   * @param {*} value No description
   * @param {*} row No description
   * @param {*} column No description
   * @returns {boolean} No description
   */
  export function generalFilterHandle(
    value: any,
    row: any,
    column: any
  ): boolean {
    const property = column.property;
    return row[property] === value;
  }
}

/**
 * 常用时间毫秒数
 */
export enum TIME {
  /**
   * 一天的毫秒数
   */
  day = 86400000,
  /**
   * 一小时的毫秒数
   */
  hour = 3600000,
  /**
   * 一分钟的毫秒数
   */
  minute = 60000,
  /**
   * 一秒钟的毫秒数
   */
  second = 1000,
  /**
   * 一个月的毫秒数, 按每年有365.25天
   */
  month = 2629800000,
  /**
   * 一年的毫秒数，按365.25天计算
   */
  year = 31557600000,
}

/**
 * 字符串全部替换
 * @param src 原字符串
 * @param find 查找字符串
 * @param replace 替换字符串
 * @param raw 是否在构建正则表达式是把find需要转义的字符进行转义
 * @returns 替换后的字符串
 */
export function replaceAll(
  src: string,
  find: string,
  replace: string = "",
  raw: boolean = true
): string {
  //  $ ---> \$
  // ( ---> \(
  // ) ---> \)
  // * ---> \*
  // + ---> \+
  // . ---> \.
  // [ ---> \[
  // ] ---> \]
  // ? ---> \?
  // \ ---> \\
  // / ---> \/
  // ^ ---> \^
  // { ---> \{
  // } ---> \}
  if (raw) {
    find = find.replace(/\$/g, "\\$");
    find = find.replace(/\(/g, "\\(");
    find = find.replace(/\)/g, "\\)");
    find = find.replace(/\*/g, "\\*");
    find = find.replace(/\+/g, "\\+");
    find = find.replace(/\./g, "\\.");
    find = find.replace(/\[/g, "\\[");
    find = find.replace(/\]/g, "\\]");
    find = find.replace(/\?/g, "\\?");
    find = find.replace(/\\/g, "\\\\");
    find = find.replace(/\//g, "\\/");
    find = find.replace(/\^/g, "\\^");
    find = find.replace(/\|/g, "\\|");
    find = find.replace(/\{/g, "\\{");
    find = find.replace(/\}/g, "\\}");
  }
  const reg = new RegExp(find, "g");
  return src.replace(reg, replace);
}

/**
 * 清屏
 * @param isNode 是否是node环境
 */
export function clearScreen(isNode?: boolean) {
  if (isNode) {
    process.stdout.write("\x1B[2J\x1B[0f");
  } else {
    console.log("\x1Bc");
  }
}

/**
 * 强制将量转换为Date对象
 * @param date 需要转换的量
 * @param defaultDate 默认值
 * @returns 转换后的Date对象
 */
export function forceToDate(date?: any, defaultDate: Date = new Date()): Date {
  if (date instanceof Date) {
    return date;
  } else {
    const _date = new Date(date);
    if (date) {
      return _date;
    } else {
      return defaultDate;
    }
  }
}
